
'''
-*- coding: utf-8 -*-
Date: 2020-9-13
Python: 3.8.5
需安装的第三方库selenium、requests、opencv-python

https://www.gaoyuanqi.cn/python-tx-sliding_block/#more
'''

import time
import random       # 随机
import requests
from cv2 import cv2 as cv    # 识别验证码

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains                       
from selenium .webdriver import ChromeOptions
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC  



class TX():

    def __init__(self):
        # 初始化
        self.driver = webdriver.Chrome()                         # 有头模式
        self.wait = WebDriverWait(driver, 10)                    # 显性等待


    def login(self):
        '''
        登录含有 腾讯滑动验证码 的页面
        以 大乐斗文字版 为例
        '''

        driver.get('http://dld.qzapp.z.qq.com/qpet/cgi-bin/phonepk?zapp_uin=&sid=b+BqyBVjL/uOqlmjH2/Kp0/GqwABVg/196790a110201==&channel=0&g_ut=1&cmd=index')
        time.sleep(0.25)
        self.账号 = wait.until(EC.presence_of_element_located((By.ID, 'u')))
        self.账号.send_keys('32343554')  # 输入账号
        time.sleep(0.25)
        self.密码 = wait.until(EC.presence_of_element_located((By.ID, 'p')))
        self.密码.send_keys("sdfgfytythb")  # 输入密码
        time.sleep(0.25)
        # 登录
        wait.until(EC.presence_of_element_located(
            (By.ID, 'go'))).click()

    def tx_code(self):
        '''
        滑动滑块
        '''

        self.login()

        # 如果元素在iframe中
        # 定位 iframe
        self.iframe = WebDriverWait(driver, 20, 0.5).until(
            EC.presence_of_element_located((By.ID, 'tcaptcha_iframe')))
        # 切换 iframe
        driver.switch_to.frame(self.iframe)

        time.sleep(0.5)

        # 验证码链接
        bk_block = wait.until(EC.presence_of_element_located(
            (By.ID, 'slideBg'))).get_attribute('src')

        if self.save_img(bk_block):

            dex = self.get_pos()
            track_list = self.get_track(dex)
            time.sleep(0.5)

            # 滑块定位
            slid_ing = wait.until(EC.presence_of_element_located(
                (By.ID, 'tcaptcha_drag_thumb')))

            # 鼠标按下
            ActionChains(driver).click_and_hold(
                on_element=slid_ing).perform()

            time.sleep(0.2)
            print('轨迹', track_list)

            for track in track_list:
                # 鼠标移动到距离当前位置（x,y）
                ActionChains(driver).move_by_offset(
                    xoffset=track, yoffset=0).perform()
            time.sleep(1)

            # 第三步,释放鼠标
            ActionChains(driver).release(
                on_element=slid_ing).perform()
            time.sleep(1)

            # 识别图片
            return True
        else:
            print('缺口图片捕获失败')
            return False

    @staticmethod
    def save_img(bk_block):
        '''
        不要用中文路径, 会报错
        bk_block为缺口图片链接,在tx_code()中获取
        '''

        try:
            # 发起请求且将响应结果赋值给img
            img = requests.get(bk_block)

            # 写入图片
            with open('./TX.jpeg', 'wb') as f:
                f.write(img.content)
            return True
        except:
            return False

    @staticmethod
    def get_pos():
        '''
        识别缺口
        注意：网页上显示的图片为缩放图片, 缩放 50 % 所以识别坐标需要 0.5
        contours                轮廓图像, 类型list
        hierarchy               每条轮廓对应的属性, 属于 numpy.ndarray 类
        cv.RETR_EXTERNAL        表示只检测外轮廓
        cv.CHAIN_APPROX_SIMPLE  压缩水平、垂直、对角线方向的元素, 只保留该方向的终点坐标, 例如一个矩形轮廓只需4个点来保存轮廓信息
        '''

        # 读取并把图像赋值给img
        image = cv.imread('./TX.jpeg')

        # 高斯模糊,(5, 5)表示高斯矩阵的长与宽都是5,标准差取0
        blurred = cv.GaussianBlur(image, (5, 5), 0)

        # 边缘检测,200, 400表最小和最大阈值
        canny = cv.Canny(blurred, 200, 400)

        contours, hierarchy = cv.findContours(
            canny, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
        for i, contour in enumerate(contours):
            # m 类型dict
            m = cv.moments(contour)
            if m['m00'] == 0:
                cx = cy = 0
            else:
                cx, cy = m['m10'] / m['m00'], m['m01'] / m['m00']

            # 判断轮廓面积和封闭轮廓的周长或曲线的长度
            if 6000 < cv.contourArea(contour) < 8000 and 370 < cv.arcLength(contour, True) < 390:
                if cx < 400:
                    continue

                # 计算轮廓的垂直边界最小矩形 x,y是矩阵左上点的坐标  w,h是矩阵的宽和高
                x, y, w, h = cv.boundingRect(contour)  # 外接矩形

                # 绘制一个矩形 (图像、矩形顶点、矩形对角线的另一个顶点、线条颜色、线条宽度)
                cv.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
                # cv.imshow('image', image)  # 显示识别结果
                print('【缺口识别】 {x}px'.format(x=x/2))
                return x/2
        return 0

    @staticmethod
    def get_track(distance):
        '''
        模拟轨迹
        '''

        # 初始位置
        distance -= 26
        # 初速度
        v = 0
        # 单位时间为0.2s来统计轨迹,轨迹即0.2内的位移
        t = 0.2
        # 位移/轨迹列表,列表内的一个元素代表0.2s的位移
        tracks = []
        # 当前的位移
        current = 0
        # 到达mid值开始减速
        mid = distance * 7 / 8

        # 先滑过一点,最后再反着滑动回来
        distance += 10
        # a = random.randint(1,3)
        while current < distance:
            if current < mid:
                # 加速度越小,单位时间的位移越小,模拟的轨迹就越多越详细
                a = random.randint(2, 4)  # 加速运动
            else:
                a = -random.randint(3, 5)  # 减速运动

            # 初速度
            v0 = v
            # 0.2秒时间内的位移
            s = v0 * t + 0.5 * a * (t ** 2)
            # 当前的位置
            current += s
            # 添加到轨迹列表
            tracks.append(round(s))

            # 速度已经达到v,该速度作为下次的初速度
            v = v0 + a * t

        # 反着滑动到大概准确位置
        for i in range(4):
            tracks.append(-random.randint(2, 3))
        for i in range(4):
            tracks.append(-random.randint(1, 3))
        return tracks




if __name__ == '__main__':

    '''
    只需在 login() 中添加含有腾讯滑动验证码的页面即可自动滑动
    '''

    # 实例化
    TX = TX()
    TX.tx_code()